package com.lingchou.cloud.process.client.controller;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.lc.common.bean.response.Result;
import com.lc.common.util.UserInfo;
import com.lingchou.cloud.process.api.controller.ProcessBaseApi;
import com.lingchou.cloud.process.api.dto.constant.FlowConstant;
import com.lingchou.cloud.process.api.dto.param.base.NodeStartParam;
import com.lingchou.cloud.process.api.dto.param.base.ProcessConfigVo;
import com.lingchou.cloud.process.api.dto.param.base.ProcessStartParamVo;
import com.lingchou.cloud.process.api.dto.param.base.TaskNode;
import com.lingchou.cloud.process.api.dto.result.ProcessInstanceDTO;
import com.lingchou.cloud.process.api.dto.result.base.NodeParamVo;
import com.lingchou.cloud.process.api.dto.result.base.ProcessConfigResultVo;
import com.lingchou.cloud.process.api.enums.CommentTypeEnum;
import com.lingchou.cloud.process.client.dao.DefinitionSupplementaryDao;
import com.lingchou.cloud.process.client.entity.DefinitionSupplementary;
import com.lingchou.cloud.process.client.service.ProcessModelService;
import com.lingchou.cloud.process.client.service.ProcessUtilService;
import com.lingchou.cloud.process.client.util.LocalDateAndLocalDateTimeUtil;
import com.lingchou.cloud.process.client.util.ThreadLocalUtil;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.IdentityService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.DeploymentQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.validation.ProcessValidator;
import org.flowable.validation.ProcessValidatorFactory;
import org.flowable.validation.ValidationError;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RestController
@Validated
public class ProcessBaseApiImpl implements ProcessBaseApi {

    private final ProcessModelService processModelService;
    private final RepositoryService repositoryService;
    private final DefinitionSupplementaryDao supplementaryDao;
    private final IdentityService identityService;
    private final RuntimeService runtimeService;
    private final ProcessUtilService processUtilService;

    public ProcessBaseApiImpl(ProcessModelService processModelService, RepositoryService repositoryService, DefinitionSupplementaryDao supplementaryDao,
                              IdentityService identityService, RuntimeService runtimeService, ProcessUtilService processUtilService) {
        this.processModelService = processModelService;
        this.repositoryService = repositoryService;
        this.supplementaryDao = supplementaryDao;
        this.identityService = identityService;
        this.runtimeService = runtimeService;
        this.processUtilService = processUtilService;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<ProcessConfigResultVo> deploy(ProcessConfigVo processConfigVo) {
        long start = System.currentTimeMillis();
        //声明BpmnModel
        BpmnModel bpmnModel = new BpmnModel();
        //声明Process
        Process process = new Process();
        process.setId(FlowConstant.ID_PREFIX + IdUtil.simpleUUID());
        //开始事件
        StartEvent startEvent = processModelService.createStartEvent(process, FlowConstant.ID_PREFIX + IdUtil.simpleUUID(), "start");
        //结束事件
        EndEvent endEvent = processModelService.createEndEvent(process, FlowConstant.ID_PREFIX + IdUtil.simpleUUID(), "end");
        //定时服务任务
        ServiceTask timeServiceTask = processModelService.createServiceTask(process, FlowConstant.ID_PREFIX + IdUtil.simpleUUID(), "timeServiceTask");
        //连线定时任务与end节点
        processModelService.createSequenceFlow(process, timeServiceTask.getId(), endEvent.getId(), FlowConstant.ID_PREFIX + IdUtil.simpleUUID(), FlowConstant.NAME_PREFIX + IdUtil.simpleUUID(), StrUtil.EMPTY);
        //当前节点
        TaskNode currNode;
        //最后一个节点ID 初始是开始节点的id
        String lastElementId = startEvent.getId();
        //配置变量
        List<DefinitionSupplementary> varList = new ArrayList<>();
        //遍历生成
        for (int i = 0; i < processConfigVo.getTaskNodes().size(); i++) {
            //赋值curr为头节点
            currNode = processConfigVo.getTaskNodes().get(i);
            //初始curr的task的id
            String currTaskId = FlowConstant.ID_PREFIX + IdUtil.simpleUUID();
            //上一节点的出口线
            String lastLineId = FlowConstant.ID_PREFIX + IdUtil.simpleUUID();
            processModelService.createSequenceFlow(process, lastElementId,
                    currTaskId, lastLineId, FlowConstant.NAME_PREFIX + lastLineId, StrUtil.EMPTY);
            //人员集合变量名
            String collectionString = FlowConstant.PARAM_PREFIX + IdUtil.simpleUUID();
            //完成条件变量名 节点类型
            String completionCondition = FlowConstant.PARAM_PREFIX + IdUtil.simpleUUID();
            //完成条件的判断方法
            String completionConditionParam = "${completeExecution.completeTask(execution,\"" + completionCondition + "\")}";
            //结束时间变量名
            String endTimeParam = FlowConstant.PARAM_PREFIX + IdUtil.simpleUUID();
            String endTimeParamString = "${execution.getVariable(\"" + endTimeParam + "\")}";
            processModelService.createUserTaskCountersign(process, currTaskId, currNode.getNodeName(),
                    collectionString, completionConditionParam, endTimeParamString, timeServiceTask.getId());
            //赋值最后一个Task
            lastElementId = currTaskId;
            //添加辅助配置表 加入变量名
            DefinitionSupplementary definitionSupplementary = new DefinitionSupplementary();
            definitionSupplementary.setId(IdUtil.simpleUUID());
            definitionSupplementary.setTaskDefId(currTaskId);
            //选人范围变量
            definitionSupplementary.setSelectionRange(currNode.getSelectionRangeKey());
            //人员集合变量名
            definitionSupplementary.setPersonCollParamName(collectionString);
            //完成条件变量名 节点类型
            definitionSupplementary.setNodeTypeParamName(completionCondition);
            //结束时间变量名
            definitionSupplementary.setDueTimeParamName(endTimeParam);
            definitionSupplementary.setCreateTime(LocalDateTime.now());
            definitionSupplementary.setUpdateTime(LocalDateTime.now());
            varList.add(definitionSupplementary);
        }
        //结束线
        processModelService.createSequenceFlow(process, lastElementId,
                endEvent.getId(), FlowConstant.ID_PREFIX + IdUtil.simpleUUID(), "endLine", StrUtil.EMPTY);
        //流程添加
        bpmnModel.addProcess(process);
        ProcessValidatorFactory processValidatorFactory = new ProcessValidatorFactory();
        ProcessValidator defaultProcessValidator = processValidatorFactory.createDefaultProcessValidator();
        // 验证失败信息的封装ValidationError
        List<ValidationError> validate = defaultProcessValidator.validate(bpmnModel);
        if (!validate.isEmpty()) {
            String errorMessage = validate.stream().map(validationError -> validationError.getActivityName() + ":" + validationError.getProblem()).collect(Collectors.joining(";"));
            return Result.failure(errorMessage);
        }
        //流程发布
        Deployment deployment = repositoryService
                .createDeployment()
                .addBpmnModel(FlowConstant.ID_PREFIX + IdUtil.simpleUUID() + FlowConstant.FILE_SUFFIX, bpmnModel)
                .name(processConfigVo.getProcessName())
                .key(processConfigVo.getBusinessKey())
                .tenantId(UserInfo.getPersonNo())
                .deploy();
        //查询流程定义
        ProcessDefinition processDefinition = getProcessDefinition(deployment.getId());
        //变量存储
        varList.forEach(definitionSupplementary -> definitionSupplementary.setProcDefId(processDefinition.getId()));
        supplementaryDao.saveAll(varList);
        System.err.println("deploy:" + (System.currentTimeMillis() - start));
        return Result.success(buildConfigResult(deployment, processDefinition.getId()));
    }

    /**
     * 查询流程定义
     *
     * @param deploymentId 流程发布ID
     * @return 流程定义
     */
    private ProcessDefinition getProcessDefinition(String deploymentId) {
        return repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploymentId).singleResult();
//                .latestVersion().singleResult();
    }

    /**
     * 封装配置返回
     *
     * @param deployment          流程发布
     * @param processDefinitionId 流程定义ID
     * @return 流程配置返回
     */
    private ProcessConfigResultVo buildConfigResult(Deployment deployment, String processDefinitionId) {
        return ProcessConfigResultVo.builder()
                .id(deployment.getId())
                .processDefId(processDefinitionId)
                .name(deployment.getName())
                .tenantId(deployment.getTenantId())
                .deployTime(LocalDateAndLocalDateTimeUtil.dateToLocalDateTime(deployment.getDeploymentTime()))
                .build();
    }

    @Override
    public Result<List<ProcessConfigResultVo>> deployments(String businessKey, Integer startNum, Integer rows) {
        //部署查询
        DeploymentQuery deploymentQuery = repositoryService.createDeploymentQuery().deploymentKey(businessKey).orderByDeploymentTime().desc();
        List<Deployment> deployments = deploymentQuery.listPage(startNum, rows);
        if (deployments == null || deployments.isEmpty()) {
            return Result.successPage(new ArrayList<>(), 0, 0);
        }
        //结果封装
        List<ProcessConfigResultVo> processConfigResultVos = deployments.stream().map(deployment -> {
            ProcessDefinition processDefinition = getProcessDefinition(deployment.getId());
            return buildConfigResult(deployment, processDefinition.getId());
        }).collect(Collectors.toList());
        return packageConfigs(processConfigResultVos, deploymentQuery, rows);
    }

    @Override
    public Result<Object> deleteDeployment(String deploymentId) {
        try {
            repositoryService.deleteDeployment(deploymentId);
        } catch (RuntimeException r) {
            return Result.failure("仍然存在运行时或历史流程实例或作业");
        }
        return Result.success();
    }

    @Override
    public Result<List<NodeParamVo>> getNodeParams(String deploymentId) {
        ProcessDefinition processDefinition = getProcessDefinition(deploymentId);
        if (processDefinition == null) {
            return Result.failure("流程不存在");
        }
        //获取userTask
        List<UserTask> elements = getElements(processDefinition.getId());
        if (elements == null || elements.isEmpty()) {
            return Result.failure("流程节点为空");
        }
        //构建返回
        List<NodeParamVo> nodeParamVos = elements.stream().map(element -> {
            DefinitionSupplementary definitionSupplementary = supplementaryDao.findByTaskDefId(element.getId());
            return buildNodeParamVo(definitionSupplementary, element.getName(), element.getId());
        }).collect(Collectors.toList());
        return Result.success(nodeParamVos);
    }

    /**
     * 获取流程定义的节点
     *
     * @param processDefinitionId 流程定义ID
     * @return 节点
     */
    private List<UserTask> getElements(String processDefinitionId) {
        Process process = repositoryService.getBpmnModel(processDefinitionId).getMainProcess();
        return process.findFlowElementsOfType(UserTask.class);
    }

    @Override
    public Result<ProcessInstanceDTO> startInstanceByDeployment(ProcessStartParamVo processStartParamVo) {
        long start = System.currentTimeMillis();
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(processStartParamVo.getDeploymentId()).singleResult();
        if (processDefinition == null) {
            return Result.failure("流程不存在");
        }
        identityService.setAuthenticatedUserId(UserInfo.getPersonNo() == null ? "SYSTEM" : UserInfo.getPersonNo());
        List<NodeStartParam> nodeStartParams = processStartParamVo.getNodeStartParams();
        //TODO 需要校验参数与流程配置准确性
        Map<String, Object> map = new HashMap<>(16);
        nodeStartParams.forEach(nodeStartParam -> {
            DefinitionSupplementary definitionSupplementary = supplementaryDao.findByTaskDefId(nodeStartParam.getTaskDefId());
            if (definitionSupplementary != null) {
                //人员集合
                map.put(definitionSupplementary.getPersonCollParamName(), nodeStartParam.getPersonColl());
                //过期时间
                map.put(definitionSupplementary.getDueTimeParamName(), nodeStartParam.getDueTimeString());
                //节点类型
                map.put(definitionSupplementary.getNodeTypeParamName(), nodeStartParam.getNodeType().name());
            }
        });
        ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinition.getId(), processStartParamVo.getBusinessKey(), map);
        //塞入ThreadLocal
        ThreadLocalUtil.putStatus(CommentTypeEnum.IN_REVIEW);
        System.err.println("启动：" + (System.currentTimeMillis() - start));
        return processUtilService.packageReturnProcessInstance(processInstance, "流程启动失败");
    }

    @Override
    public Result<String> revokeProcess(String processInstanceId, String operator) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();
        if (processInstance == null) {
            return Result.failure("流程实例不存在");
        }
        runtimeService.deleteProcessInstance(processInstanceId, FlowConstant.REVOKE_REASON);
        //塞进ThreadLocal
        ThreadLocalUtil.putStatus(CommentTypeEnum.REVOKE);
        return Result.success("撤销成功");
    }

    /**
     * 构建 NodeParamVo
     *
     * @param definitionSupplementary 配置补充对象
     * @param name                    节点名称
     */
    private NodeParamVo buildNodeParamVo(DefinitionSupplementary definitionSupplementary, String name, String taskDefId) {
        return NodeParamVo.builder()
                .nodeName(name)
                .selectionRangeKey(definitionSupplementary == null ? null : definitionSupplementary.getSelectionRange())
                .taskDefId(taskDefId)
                .build();
    }

    /**
     * 封装返回配置列表
     *
     * @param processConfigResultVos 配置集合
     * @param deploymentQuery        部署查询
     * @param rows                   本页数据量
     * @return 封装结果
     */
    private Result<List<ProcessConfigResultVo>> packageConfigs(List<ProcessConfigResultVo> processConfigResultVos,
                                                               DeploymentQuery deploymentQuery, Integer rows) {
        long total = deploymentQuery.count();
        long pages = total % rows > 0 ? total / rows + 1 : total / rows;
        return Result.successPage(processConfigResultVos, total, pages);
    }


}
